package org.xmlbinder;

import org.xmlpull.v1.XmlSerializer;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;

import java.util.HashMap;
import java.util.List;
import java.util.ArrayList;
import java.lang.reflect.Method;
import java.io.IOException;

/**This class uses the metadata on the Beans to inject data into a bean    . It has two purposes
 *  1) Acts as the data structure for caching the information of a bean. The data is cached very efficiently
 *  to make the marshalling/unmarshalling extremely simple and fast.
 *  2) Contains the logic for unmarshalling
 * @author Noble Paul (noble.paul@gmail.com)
 *         Date: Dec 15, 2007
 *         Time: 2:56:42 PM
 */
public class BeanInjector {
    protected Class clz;
    boolean isSimpleType = false, hasWildWildCard = false;
    FieldAnnotationProcessor cdata,attrWildCard,elemWildcard,parentField;
    HashMap<String, FieldAnnotationProcessor> attrFields = new HashMap<String, FieldAnnotationProcessor>(),
            elemFields = new HashMap<String, FieldAnnotationProcessor>(),startTagMethods,endTagMethods;
    protected String name;
    Method setParentMethod;

    /**This is the method which actually reads and injects data into a bean using recursive logic
     * @param xpp The XPP object
     * @param obj The object to which data is injected
     * @param parentTagName the enclosing tag name
     * @throws java.io.IOException
     * @throws org.xmlpull.v1.XmlPullParserException
     */
    void inject(XmlPullParser xpp, Object obj, String parentTagName, XmlBinder xb)
            throws IOException, XmlPullParserException {
        if (attrFields.size() > 0) {
            int attrCount = xpp.getAttributeCount();
            for (int i = 0; i < attrCount; i++) {
                String name = xpp.getAttributeName(i);
                FieldAnnotationProcessor bf = attrFields.get(name);
                if (bf == null) bf = attrWildCard;
                if (bf != null) {
                    String val = xpp.getAttributeValue(i);
                    if (val != null) bf.checkTypeAndSet(obj, bf.unmarshal(val,xb), name, xb);
                }
            }
        }
        String tname = null;
        boolean cdataProcessed = false;
        while (true) {
            int eventType = xpp.next();
            if (eventType == XmlPullParser.START_TAG) {
                tname = xpp.getName();
                if(startTagMethods !=null) {
                    FieldAnnotationProcessor startTagFld = startTagMethods.get(tname);
                    if (startTagFld != null) {
                        try {
                            startTagFld.setter.invoke(obj);
                        } catch (Exception e) {
                            throw new RuntimeException(e);
                        }
                    }
                }
                FieldAnnotationProcessor aField = elemFields.get(tname);
                if (aField == null) aField = elemWildcard;
                if (aField == null && hasWildWildCard) aField = parentField;
                if (aField == null) {
                    skipTag(xpp);
                    continue;
                }
                if (aField.bean.isSimpleType) {
                    aField.bean.inject(xpp, obj, tname,xb);
                    if(endTagMethods !=null ) callStartTagForSimpleType(tname, obj);
                } else {
                    aField.injectElem(xpp, obj, tname, xb);
                }
            } else if (eventType == XmlPullParser.TEXT && cdata != null){
                cdataProcessed = true;
                cdata.checkTypeAndSet(obj, cdata.unmarshal(xpp.getText(),xb), parentTagName, xb);
            } else if (eventType == XmlPullParser.END_TAG) {
                if (!cdataProcessed && this.parentField !=null  && this.parentField.parent.endTagMethods !=null)
                    callEndTag(xpp.getName(), obj);
                break;
            }
        }
    }

    private void callStartTagForSimpleType(String tname, Object obj) {
        FieldAnnotationProcessor endTagFld = endTagMethods.get(tname);
        if (endTagFld != null) {
            try {
                endTagFld.setter.invoke(obj);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    }

    private void callEndTag(String tag, Object obj) {
        FieldAnnotationProcessor endTagFld = parentField.parent.endTagMethods.get(tag);
        if (endTagFld != null) {
            try {
                endTagFld.setter.invoke(obj);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    }

    /**if there is no mapping for an element in xml  skip it totally
     * @param xpp
     * @throws java.io.IOException
     * @throws org.xmlpull.v1.XmlPullParserException
     */
    private static void skipTag(XmlPullParser xpp) throws IOException, XmlPullParserException {
        int type;
        while ((type = xpp.next()) != XmlPullParser.END_TAG) {
            if (type == XmlPullParser.START_TAG) skipTag(xpp);
        }
    }

    public String getName() {
        return name;
    }

    /**uses an XmlSerializer to deserialize a bean to xml. Invokes the fields to actually carry out the tasks
     * @param t The stack of tags
     * @param obj the actual bean/subbean
     * @param ser the xpp seraializer
     * @param xub
     * @throws java.io.IOException
     */
    void unbind(TagStack t, Object obj, XmlSerializer ser, XmlUnbinder xub)
            throws IOException {
        List<FieldInjector> l = new ArrayList(attrFields.values());
        if (cdata != null) l.add(cdata);
        l.addAll(elemFields.values());
        for (FieldInjector bf : l) {
            bf.unbind(t, obj, ser, xub);
        }
    }
}
